import direct.directbase.DirectStart #starts Panda
from pandac.PandaModules import *    #basic Panda modules
from direct.showbase.DirectObject import DirectObject  #for event handling
from direct.actor.Actor import Actor #for animated models
from direct.interval.IntervalGlobal import *  #for compound intervals
from direct.task import Task         #for update functions
import sys, math, random

class Player(DirectObject):
    def __init__(self, world):
        #turn off default mouse control, otherwise camera isn't repositionable
        #base.disableMouse()
        self.position = [0, -15, 6]
        self.rotation = [0, -15, 0]
        self.attachControls()
        self.loadModel()
        self.setUpCamera()

        self.keyMap = {"left":0, "right":0, "forward":0, "backward":0}
        taskMgr.add(self.mouseUpdate, "mouse-task")
        taskMgr.add(self.moveUpdate, "move-task")
        self.prevtime = 0
        self.isMoving = False
        
        self.speed = 25
        
    def setKey(self, key, value):
        self.keyMap[key] = value
        
    def loadModel(self):
        """ make the nodepath for player """
        self.node = loader.loadModel("panda")
        self.node.reparentTo(render)
        self.node.setPos(0,0,2)
        self.node.setScale(.05)
        
    def setUpCamera(self):
        """ puts camera at the players node """
        pl =  base.cam.node().getLens()
        pl.setFov(70)
        base.cam.node().setLens(pl)
        base.camera.reparentTo(self.node)

    def mouseUpdate(self,task):
        """ this task updates the mouse """
        md = base.win.getPointer(0)
        x = md.getX()
        y = md.getY()
        if base.win.movePointer(0, base.win.getXSize()/2, base.win.getYSize()/2):
            self.node.setH(self.node.getH() -  (x - base.win.getXSize()/2)*0.1)
            self.node.setP(self.node.getP() - (y - base.win.getYSize()/2)*0.1)
            
        return task.cont

    def moveUpdate(self, task):
        elapsed = task.time - self.prevtime
        self.rotation = self.node.getHpr()
        self.vel_vec = [0, 0]
        self.angle = 0
        self.moving = False
        
        if self.keyMap["forward"] or self.keyMap["left"] or self.keyMap["right"] or self.keyMap["backward"]:
            self.moving = True

            if self.keyMap["forward"]:
                self.angle += 90
            if self.keyMap["backward"]:
                self.angle -= 90
            if self.keyMap["left"]:
                self.angle += 180
            if self.keyMap["right"]:
                self.angle += 0
                if(self.angle == 180):
                    self.moving = False
            elif(self.angle == 0):
                self.moving = False
            
        if(self.moving):
            self.vel_vec = [self.speed*elapsed*math.cos(math.radians(self.rotation[0]+self.angle)),
                            self.speed*elapsed*math.sin(math.radians(self.rotation[0]+self.angle))]

        self.position[0] += self.vel_vec[0]
        self.position[1] += self.vel_vec[1]
            
        self.node.setPos(self.position[0], self.position[1], self.position[2])
        
        self.prevtime = task.time
        return Task.cont
        
    def attachControls(self):
        """ attach key events """
        self.accept("escape", sys.exit) #message name, function to call, optional list of arguments
        self.accept("w", self.setKey, ["forward", 1])
        self.accept("s", self.setKey, ["backward", 1])
        self.accept("d", self.setKey, ["right", 1])
        self.accept("a", self.setKey, ["left", 1])
        self.accept("w-up", self.setKey, ["forward", 0])
        self.accept("d-up", self.setKey, ["right", 0])
        self.accept("a-up", self.setKey, ["left", 0])
        self.accept("s-up", self.setKey, ["backward", 0])
        
        
class World(DirectObject): #necessary to accept events
    #initializer
    def __init__(self):
        self.loadModels()
        self.setupLights()
        self.setupCollisions()
        self.accept("escape", sys.exit) #message name, function to call, optional list of arguments
        
        props = WindowProperties()
        props.setCursorHidden(True)
        props.setMouseMode(1)
        props.setFullscreen(True)
        base.win.requestProperties(props)
        self.player = Player(self)
        
        
    def loadModels(self):
        """load the default panda and env models"""
        self.panda = Actor("panda-model", {"walk":"panda-walk4"})
        self.panda.reparentTo(render)
        self.panda.setScale(0.005)
        self.panda.setH(180)
        self.env = loader.loadModel("environment")
        self.env.reparentTo(render)
        self.env.setScale(0.25)
        self.env.setPos(-8, 42, 0)
        #load targets
        self.targets = []
        for i in range(10):
            target = loader.loadModel("smiley")
            target.setScale(.5)
            target.setPos(random.uniform(-20, 20), random.uniform(-15, 15), 2)
            target.reparentTo(render)
            self.targets.append(target)
        
    def setupLights(self):
        """loads initial lighting"""
        self.dirLight = DirectionalLight("dirLight")
        self.dirLight.setColor((.6, .6, .6, 1))
        #create a NodePath, and attach it directly into the scene
        self.dirLightNP = render.attachNewNode(self.dirLight)
        self.dirLightNP.setHpr(0, -25, 0)
        #the NP that calls setLight is what gets lit
        render.setLight(self.dirLightNP)
        # clearLight() turns it off
        
        self.ambientLight = AmbientLight("ambientLight")
        self.ambientLight.setColor((.25, .25, .25, 1))
        self.ambientLightNP = render.attachNewNode(self.ambientLight)
        render.setLight(self.ambientLightNP)
        
        
    def setupCollisions(self):
        #make a collision traverser
        base.cTrav = CollisionTraverser()
        #set the collision handler to send event messages on collision
        self.cHandler = CollisionHandlerEvent()
        # %in is substituted with the name of the into object
        self.cHandler.setInPattern("ate-%in")
        
        #panda collider
        cSphere = CollisionSphere((0,0,0), 500) #because the panda is scaled down
        cNode = CollisionNode("panda")
        cNode.addSolid(cSphere)
        #set panda to only be a "from" object
        cNode.setIntoCollideMask(BitMask32.allOff())
        cNodePath = self.panda.attachNewNode(cNode)
        #cNodePath.show()
        base.cTrav.addCollider(cNodePath, self.cHandler)
        
        #target colliders
        for target in self.targets:
            cSphere = CollisionSphere((0,0,0), 2)
            cNode = CollisionNode("smiley")
            cNode.addSolid(cSphere)
            cNodePath = target.attachNewNode(cNode)
            #cNodePath.show()
        
        
w = World()
run()